using System;
using System.Collections;
using System.Text;
using NUnit.Framework;

namespace NUnit.Behave
{
    [TestFixture]
    public abstract class BehavioralFixture
    {
        protected StringBuilder messageHeap;
        protected Hashtable actionCatalog;
        protected string lastAction;
        protected string scenarioTitle;

        //TODO: Turn this into a property so you can inject a logging framework.
        protected bool writeToConsole = true;

        private const string GIVEN = "Given";
        private const string WHEN = "When";
        private const string THEN = "Then";
        private const string SPERATOR_LINE = "-----------------------------------------------------";


        [SetUp]
        protected virtual void SetUpFixture()
        {
            messageHeap = new StringBuilder();
            actionCatalog = new Hashtable();
        }

        [TearDown]
        protected virtual void TearDownFixture()
        {
            WriteToLog();
            actionCatalog.Clear();
        }

        private void WriteToLog()
        {
            //TODO: Write to logging framework.
            if (writeToConsole)
            {
                messageHeap.Append(SPERATOR_LINE, 0, SPERATOR_LINE.Length);
                messageHeap.Append(SPERATOR_LINE);
                Console.Write(messageHeap);
            }
        }

        protected ScenarioConjunction Scenario(string scenario)
        {
            scenarioTitle = string.Format("\tScenario: {0}", scenario);
            return new ScenarioConjunction(this);
        }

        protected void Given<T>(string message, T actionValue, Action<T> delegateAction)
        {
            try
            {
                InvokeDelegateAction(GIVEN, actionValue, delegateAction, message);
                CatalogAction(GIVEN, message, delegateAction);
            }
            catch (Exception e)
            {
                throw BehaviorException(GIVEN, actionValue, message, e);
            }
        }

        protected GivenBehaviorConjunction Given<T>(string message, T actionValue)
        {
            try
            {
                InvokeActionFromCatalog(actionValue, GIVEN, message);
                return new GivenBehaviorConjunction(this);
            }
            catch (Exception e)
            {
                throw BehaviorException(GIVEN, actionValue, message, e);
            }
        }

        protected void When<T>(string message, T actionValue, Action<T> delegateAction)
        {
            try
            {
                InvokeDelegateAction(WHEN, actionValue, delegateAction, message);
                CatalogAction(WHEN, message, delegateAction);
            }
            catch (Exception e)
            {
                throw BehaviorException(WHEN, actionValue, message, e);
            }
        }

        protected WhenBehaviorConjunction When<T>(string message, T actionValue)
        {
            try
            {
                InvokeActionFromCatalog(actionValue, WHEN, message);
                return new WhenBehaviorConjunction(this);
            }
            catch (Exception e)
            {
                throw BehaviorException(WHEN, actionValue, message, e);
            }
        }

        protected void Then<T>(string message, T actionValue, Action<T> delegateAction)
        {
            try
            {
                InvokeDelegateAction(THEN, actionValue, delegateAction, message);
                CatalogAction(THEN, message, delegateAction);
            }
            catch (Exception e)
            {
                throw BehaviorException(THEN, actionValue, message, e);
            }
        }

        protected ThenBehaviorConjunction Then<T>(string message, T actionValue)
        {
            try
            {
                InvokeActionFromCatalog(actionValue, THEN, message);
                return new ThenBehaviorConjunction(this);
            }
            catch (Exception e)
            {
                throw BehaviorException(THEN, actionValue, message, e);
            }
        }

        private void InvokeActionFromCatalog<T>(T actionValue, string behaviorAction, string message)
        {
            string key = string.Format("{0} {1}", behaviorAction, message);

            if (actionCatalog.Contains(key))
            {
                InvokeDelegateAction(behaviorAction, actionValue, (Action<T>) actionCatalog[key], message);
            }
            else
            {
                AddMessageToMessageHeapAsPending(actionValue, message, behaviorAction);
            }
        }

        private void CatalogAction<T>(string action, string message, Action<T> delegateAction)
        {
            actionCatalog.Add(string.Format("{0} {1}", action, message), delegateAction);
        }

        private void InvokeDelegateAction<T>(string methodBehavior, T actionValue, Action<T> delegateAction,
                                             string message)
        {
            delegateAction(actionValue);
            AddMessageToMessageHeap(actionValue, message, methodBehavior);
        }

        private ArgumentException BehaviorException<T>(string methodBehavior, T actionValue, string message, Exception e)
        {
            AddMessageToMessageHeap(actionValue, message, methodBehavior);
            return new ArgumentException(messageHeap.ToString(), e);
        }

        private void AddMessageToMessageHeap<T>(T actionValue, string message, string methodBehavior)
        {
            messageHeap.AppendLine(
                string.Format("\t\t{0} {1}: {2}", LogLastAction(methodBehavior), message, actionValue));
        }

        private void AddMessageToMessageHeapAsPending<T>(T actionValue, string message, string behaviorAction)
        {
            messageHeap.AppendLine(
                string.Format("\t\t{0} {1}: {2} !PENDING Delegate IMPLEMENTATION!", LogLastAction(behaviorAction), message,
                              actionValue));
        }

        private string LogLastAction(string behaviorAction)
        {
            if (IsScenarioNameNeeded(behaviorAction))
            {
                messageHeap.AppendLine();
                messageHeap.AppendLine(scenarioTitle);
            }

            return CheckIfConjuctionIsNeeded(behaviorAction);
        }

        private bool IsScenarioNameNeeded(string behaviorAction)
        {
            return (lastAction == THEN && behaviorAction == GIVEN) || string.IsNullOrEmpty(lastAction);
        }

        private string CheckIfConjuctionIsNeeded(string behaviorAction)
        {
            string returnValue = behaviorAction;

            if (lastAction == behaviorAction)
            {
                returnValue = "And";
            }

            lastAction = behaviorAction;
            return returnValue;
        }

        public class BehaviorConjunction
        {
            protected BehavioralFixture behavioralAbstractFixture;


            public BehaviorConjunction(BehavioralFixture behavioralAbstractFixture)
            {
                this.behavioralAbstractFixture = behavioralAbstractFixture;
            }
        }

        public class ScenarioConjunction : BehaviorConjunction
        {
            public ScenarioConjunction(BehavioralFixture behavioralAbstractFixture)
                : base(behavioralAbstractFixture)
            {
            }

            public GivenBehaviorConjunction Given<T>(string message, T actionValue)
            {
                return behavioralAbstractFixture.Given(message, actionValue);
            }
        }

        public class GivenBehaviorConjunction : BehaviorConjunction
        {
            public GivenBehaviorConjunction(BehavioralFixture behavioralAbstractFixture)
                : base(behavioralAbstractFixture)
            {
            }

            public GivenBehaviorConjunction And<T>(string message, T actionValue)
            {
                return behavioralAbstractFixture.Given(message, actionValue);
            }

            public WhenBehaviorConjunction When<T>(string message, T actionValue)
            {
                return behavioralAbstractFixture.When(message, actionValue);
            }
        }

        public class WhenBehaviorConjunction : BehaviorConjunction
        {
            public WhenBehaviorConjunction(BehavioralFixture behavioralAbstractFixture)
                : base(behavioralAbstractFixture)
            {
            }

            public WhenBehaviorConjunction And<T>(string message, T actionValue)
            {
                return behavioralAbstractFixture.When(message, actionValue);
            }

            public ThenBehaviorConjunction Then<T>(string message, T actionValue)
            {
                return behavioralAbstractFixture.Then(message, actionValue);
            }
        }

        public class ThenBehaviorConjunction : BehaviorConjunction
        {
            public ThenBehaviorConjunction(BehavioralFixture behavioralAbstractFixture)
                : base(behavioralAbstractFixture)
            {
            }

            public ThenBehaviorConjunction And<T>(string message, T actionValue)
            {
                return behavioralAbstractFixture.Then(message, actionValue);
            }
        }

        protected StoryConjunction Story(string story_title)
        {
            messageHeap.AppendLine(string.Format("Story: {0}", story_title));
            return new StoryConjunction(this);
        }

        public class StoryConjunction : BehaviorConjunction
        {
            public StoryConjunction(BehavioralFixture behavioralAbstractFixture) : base(behavioralAbstractFixture)
            {
            }

            public As_A_StoryConjunction As_a(string as_a_UserRole)
            {
                return behavioralAbstractFixture.writeAsA(as_a_UserRole);
            }
        }

        public class As_A_StoryConjunction : BehaviorConjunction
        {
            public As_A_StoryConjunction(BehavioralFixture behavioralAbstractFixture) : base(behavioralAbstractFixture)
            {
            }

            public I_Want_StoryConjunction I_Want(string i_want_Feature)
            {
                return behavioralAbstractFixture.writeIWant(i_want_Feature);
            }
        }

        public class I_Want_StoryConjunction : BehaviorConjunction
        {
            public I_Want_StoryConjunction(BehavioralFixture behavioralAbstractFixture) : base(behavioralAbstractFixture)
            {
            }

            public So_That_StoryConjuction So_That(string so_that_BusinessBenefit)
            {
                return behavioralAbstractFixture.writeSoThat(so_that_BusinessBenefit);
            }
        }

        public class So_That_StoryConjuction : BehaviorConjunction
        {
            public So_That_StoryConjuction(BehavioralFixture behavioralAbstractFixture) : base(behavioralAbstractFixture)
            {
            }

            public ScenarioConjunction Scenario(string scenario)
            {
                return behavioralAbstractFixture.Scenario(scenario);
            }
        }

        protected As_A_StoryConjunction writeAsA(string as_a_UserRole)
        {
            messageHeap.AppendLine(string.Format("Nerative: As a {0} ", as_a_UserRole));
            return new As_A_StoryConjunction(this);
        }

        public I_Want_StoryConjunction writeIWant(string i_want_Feature)
        {
            messageHeap.AppendLine(string.Format("\tI want {0} ", i_want_Feature));
            return new I_Want_StoryConjunction(this);
        }

        public So_That_StoryConjuction writeSoThat(string so_that_BusinessBenefit)
        {
            messageHeap.AppendLine(string.Format("\tso that {0}.", so_that_BusinessBenefit));
            return new So_That_StoryConjuction(this);
        }
    }


}